/*
 * Copyright 2023-2025 Dozingfiretruck
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "nes.h"
#include <xtensa/hal.h>

// 性能分析开关：设置为 1 启用详细的时间戳打印
#define NES_PERF_PROFILE_ENABLE    1

// CPU 频率（MHz），用于将周期数转换为时间
#define NES_CPU_FREQ_MHZ           156

// 性能分析宏定义
#if (NES_PERF_PROFILE_ENABLE == 1)
    // 获取 CPU 周期计数
    #define PERF_GET_TIME()         xthal_get_ccount()
    // 计算时间差（微秒），注意：CCount 单位是周期，需要除以频率
    // 注意：xthal_get_ccount() 返回的是 32 位值，可能会回绕
    #define PERF_CALC_US(start, end)    (((uint32_t)(end) >= (uint32_t)(start)) ? \
                                         (((uint32_t)(end) - (uint32_t)(start)) * 1000 / NES_CPU_FREQ_MHZ) : \
                                         (((0xFFFFFFFF - (uint32_t)(start) + (uint32_t)(end) + 1) * 1000 / NES_CPU_FREQ_MHZ)))
    // 计算时间差（毫秒）
    #define PERF_CALC_MS(start, end)    (((uint32_t)(end) >= (uint32_t)(start)) ? \
                                         (((uint32_t)(end) - (uint32_t)(start)) / NES_CPU_FREQ_MHZ) : \
                                         (((0xFFFFFFFF - (uint32_t)(start) + (uint32_t)(end) + 1) / NES_CPU_FREQ_MHZ)))
#else
    #define PERF_GET_TIME()         0
    #define PERF_CALC_US(start, end)    0
    #define PERF_CALC_MS(start, end)     0
#endif

//https://www.nesdev.org/pal.txt

static nes_color_t nes_palette[]={
#if (NES_COLOR_DEPTH == 32) // ARGB8888
    0xFF757575, 0xFF271B8F, 0xFF0000AB, 0xFF47009F, 0xFF8F0077, 0xFFB0013, 0xFFA70000, 0xFF7F0B00,0xFF432F00, 0xFF004700, 0xFF005100, 0xFF003F17, 0xFF1B3F5F, 0xFF000000, 0xFF000000, 0xFF000000,
    0xFFBCBCBC, 0xFF0073EF, 0xFF233BEF, 0xFF8300F3, 0xFFBF00BF, 0xFF7005B, 0xFFDB2B00, 0xFFCB4F0F,0xFF8B7300, 0xFF009700, 0xFF00AB00, 0xFF00933B, 0xFF00838B, 0xFF000000, 0xFF000000, 0xFF000000,
    0xFFFFFFFF, 0xFF3FBFFF, 0xFF5F97FF, 0xFFA78BFD, 0xFFF77BFF, 0xFFF77B7, 0xFFFF7763, 0xFFFF9B3B,0xFFF3BF3F, 0xFF83D313, 0xFF4FDF4B, 0xFF58F898, 0xFF00EBDB, 0xFF000000, 0xFF000000, 0xFF000000,
    0xFFFFFFFF, 0xFFABE7FF, 0xFFC7D7FF, 0xFFD7CBFF, 0xFFFFC7FF, 0xFFFC7DB, 0xFFFFBFB3, 0xFFFFDBAB,0xFFFFE7A3, 0xFFE3FFA3, 0xFFABF3BF, 0xFFB3FFCF, 0xFF9FFFF3, 0xFF000000, 0xFF000000, 0xFF000000,
#elif (NES_COLOR_DEPTH == 16)
#if (NES_COLOR_SWAP == 0) // RGB565
    0x73AE, 0x20D1, 0x0015, 0x4013, 0x880E, 0x0802, 0xA000, 0x7840,0x4160, 0x0220, 0x0280, 0x01E2, 0x19EB, 0x0000, 0x0000, 0x0000,
    0xBDF7, 0x039D, 0x21DD, 0x801E, 0xB817, 0x000B, 0xD940, 0xCA61,0x8B80, 0x04A0, 0x0540, 0x0487, 0x0411, 0x0000, 0x0000, 0x0000,
    0xFFFF, 0x3DFF, 0x5CBF, 0xA45F, 0xF3DF, 0x0BB6, 0xFBAC, 0xFCC7,0xF5E7, 0x8682, 0x4EE9, 0x5FD3, 0x075B, 0x0000, 0x0000, 0x0000,
    0xFFFF, 0xAF3F, 0xC6BF, 0xD65F, 0xFE3F, 0x0E3B, 0xFDF6, 0xFED5,0xFF34, 0xE7F4, 0xAF97, 0xB7F9, 0x9FFE, 0x0000, 0x0000, 0x0000,
#else // RGB565_SWAP
    0xAE73, 0xD120, 0x1500, 0x1340, 0x0E88, 0x0208, 0x00A0, 0x4078,0x6041, 0x2002, 0x8002, 0xE201, 0xEB19, 0x0000, 0x0000, 0x0000,
    0xF7BD, 0x9D03, 0xDD21, 0x1E80, 0x17B8, 0x0B00, 0x40D9, 0x61CA,0x808B, 0xA004, 0x4005, 0x8704, 0x1104, 0x0000, 0x0000, 0x0000,
    0xFFFF, 0xFF3D, 0xBF5C, 0x5FA4, 0xDFF3, 0xB60B, 0xACFB, 0xC7FC,0xE7F5, 0x8286, 0xE94E, 0xD35F, 0x5B07, 0x0000, 0x0000, 0x0000,
    0xFFFF, 0x3FAF, 0xBFC6, 0x5FD6, 0x3FFE, 0x3B0E, 0xF6FD, 0xD5FE,0x34FF, 0xF4E7, 0x97AF, 0xF9B7, 0xFE9F, 0x0000, 0x0000, 0x0000,
#endif /* NES_COLOR_SWAP */
#endif /* NES_COLOR_DEPTH */
};

nes_t* nes_init(void){
	printf("nes_t size %d\r\n",sizeof(nes_t));
    nes_t* nes = (nes_t *)nes_malloc(sizeof(nes_t));
    if (nes == NULL) {
        return NULL;
    }
    nes_memset(nes, 0, sizeof(nes_t));
    nes_initex(nes);
    return nes;
}

int nes_deinit(nes_t *nes){
    nes->nes_quit = 1;
    nes_deinitex(nes);
    if (nes){
        nes_free(nes);
        nes = NULL;
    }
    return NES_OK;
}

static inline void nes_palette_generate(nes_t* nes){
    for (uint8_t i = 0; i < 32; i++) {
        nes->nes_ppu.pal.palette[i] = nes_palette[nes->nes_ppu.palette_indexes[i]];
    }
    for (uint8_t i = 1; i < 8; i++){
        nes->nes_ppu.pal.palette[4 * i] = nes->nes_ppu.pal.palette[0];
    }
}

static void nes_render_background_line(nes_t* nes,uint16_t scanline,nes_color_t* draw_data){
    (void)scanline;
    uint8_t p = 0;
    int8_t m = 7 - nes->nes_ppu.write_latch.x;
    const uint8_t dx = (const uint8_t)nes->nes_ppu.reg_v.v.coarse_x;
    const uint8_t dy = (const uint8_t)nes->nes_ppu.reg_v.v.fine_y;
    const uint8_t tile_y = (const uint8_t)nes->nes_ppu.reg_v.v.coarse_y;
    uint8_t nametable_id = (uint8_t)nes->nes_ppu.reg_v.v.nametable;
    for (uint8_t tile_x = dx; tile_x < 32; tile_x++){
        const uint8_t pattern_id = nes->nes_ppu.banks.bank_split.name_table[nametable_id][tile_x + (tile_y << 5)];
        // if (pattern_id == 0x40){
        //     uint8_t tile_x1 = (tile_x + 1) & 0x1F;
        //     NES_LOG_DEBUG("scanline:%d pattern_id:0x%02x dx:%d dy:%d tile_x:%d tile_y:%d\n",scanline,pattern_id,dx,dy,tile_x,tile_y);
        //     NES_LOG_DEBUG("tile_x1:%d\n",tile_x1);
        // }
        // if (scanline == 170 && tile_x == 10)
        // {
        //     printf("scanline:%d pattern_id:0x%02x dx:%d dy:%d tile_x:%d tile_y:%d\n",scanline,pattern_id,dx,dy,tile_x,tile_y);
        // }
        
        const uint8_t* bit0_p = nes->nes_ppu.banks.bank_split.pattern_table[nes->nes_ppu.reg_ctrl.ctrl_bits.CTRL_B ? 4 : 0] + pattern_id * 16;
        const uint8_t* bit1_p = bit0_p + 8;
        const uint8_t bit0 = bit0_p[dy];
        const uint8_t bit1 = bit1_p[dy];
        const uint8_t attribute = nes->nes_ppu.banks.bank_split.name_table[nametable_id][960 + ((tile_y >> 2) << 3) + (tile_x >> 2)];
        // 1:D4-D5/D6-D7 0:D0-D1/D2-D3
        // 1:D2-D3/D6-D7 0:D0-D1/D4-D5
        const uint8_t high_bit = ((attribute >> (((tile_y & 2) << 1) | (tile_x & 2))) & 3) << 2;
        for (; m >= 0; m--){
            uint8_t low_bit = ((bit0 >> m) & 0x01) | ((bit1 >> m)<<1 & 0x02);
            uint8_t palette_index = (high_bit & 0x0c) | low_bit;
            draw_data[p++] = nes->nes_ppu.pal.pal_split.background_palette[palette_index];
        }
        m = 7;
    }
    nametable_id ^= 1;
    for (uint8_t tile_x = 0; tile_x <= dx; tile_x++){
        const uint8_t pattern_id = nes->nes_ppu.banks.bank_split.name_table[nametable_id][tile_x + (tile_y << 5)];
        const uint8_t* bit0_p = nes->nes_ppu.banks.bank_split.pattern_table[nes->nes_ppu.reg_ctrl.ctrl_bits.CTRL_B ? 4 : 0] + pattern_id * 16;
        const uint8_t* bit1_p = bit0_p + 8;
        const uint8_t bit0 = bit0_p[dy];
        const uint8_t bit1 = bit1_p[dy];
        const uint8_t attribute = nes->nes_ppu.banks.bank_split.name_table[nametable_id][960 + ((tile_y >> 2) << 3) + (tile_x >> 2)];
        // 1:D4-D5/D6-D7 0:D0-D1/D2-D3
        // 1:D2-D3/D6-D7 0:D0-D1/D4-D5
        const uint8_t high_bit = ((attribute >> (((tile_y & 2) << 1) | (tile_x & 2))) & 3) << 2;
        uint8_t skew = 0;
        if (tile_x == dx){
            if (nes->nes_ppu.write_latch.x){
                skew = 8 - nes->nes_ppu.write_latch.x;
            }else
                break;
        }
        for (; m >= skew; m--){
            const uint8_t low_bit = ((bit0 >> m) & 0x01) | ((bit1 >> m)<<1 & 0x02);
            const uint8_t palette_index = (high_bit & 0x0c) | low_bit;
            draw_data[p++] = nes->nes_ppu.pal.pal_split.background_palette[palette_index];
        }
        m = 7;
    }
}

static void nes_render_sprite_line(nes_t* nes,uint16_t scanline,nes_color_t* draw_data){
    const nes_color_t background_color = nes->nes_ppu.pal.pal_split.background_palette[0];
    uint8_t sprite[8] = {0};
    uint8_t sprite_numbers = 0;
    const uint8_t sprite_size = nes->nes_ppu.reg_ctrl.ctrl_bits.CTRL_H?16:8;

    // 閬嶅巻鏄剧ず鐨勭簿鐏靛拰妫�祴鏄惁绮剧伒婧㈠嚭
    for (uint8_t i = 0; i < 64; i++){
        if (nes->nes_ppu.oam.sprite_info[i].y >= 0xEF){
            continue;
        }
        uint8_t sprite_y = (uint8_t)(nes->nes_ppu.oam.sprite_info[i].y + 1);
        if (scanline < sprite_y || scanline >= sprite_y + sprite_size){
            continue;
        }
        if (sprite_numbers==8){
            nes->nes_ppu.reg_status.status_bits.STATUS_O = 1;
            break;
        }
        sprite[sprite_numbers++]=i;
    }
    // 鏄剧ず绮剧伒
    for (uint8_t sprite_number = sprite_numbers; sprite_number > 0; sprite_number--){
        const uint8_t sprite_id = sprite[sprite_number-1];
        const sprite_info_t sprite_info = nes->nes_ppu.oam.sprite_info[sprite_id];
        const uint8_t sprite_y = (uint8_t)(sprite_info.y + 1);
        const uint8_t* sprite_bit0_p = nes->nes_ppu.banks.bank_split.pattern_table[nes->nes_ppu.reg_ctrl.ctrl_bits.CTRL_H?((sprite_info.u_tile.tile.pattern_8x16)?4:0):(nes->nes_ppu.reg_ctrl.ctrl_bits.CTRL_S?4:0)] \
                                        + (nes->nes_ppu.reg_ctrl.ctrl_bits.CTRL_H?(sprite_info.u_tile.tile.tile_index_8x16 << 1 ):(sprite_info.u_tile.tile_index_number)) * 16;
        const uint8_t* sprite_bit1_p = sprite_bit0_p + 8;

        uint8_t dy = (uint8_t)(scanline - sprite_y);

        if (nes->nes_ppu.reg_ctrl.ctrl_bits.CTRL_H){
            if (sprite_info.u_attr.attr_bits.flip_v){
                if (dy < 8){
                    sprite_bit0_p +=16;
                    sprite_bit1_p +=16;
                    dy = sprite_size - dy - 1 -8;
                }else{
                    dy = sprite_size - dy - 1;
                }
            }else{
                if (dy > 7){
                    sprite_bit0_p +=16;
                    sprite_bit1_p +=16;
                    dy-=8;
                }
            }
        }else{
            if (sprite_info.u_attr.attr_bits.flip_v){
                dy = sprite_size - dy - 1;
            }
        }

        const uint8_t sprite_bit0 = sprite_bit0_p[dy];
        const uint8_t sprite_bit1 = sprite_bit1_p[dy];
#if (NES_FRAME_SKIP != 0)
        if(nes->nes_frame_skip_count == 0)
#endif
        {
            uint8_t p = sprite_info.x;
            if (sprite_info.u_attr.attr_bits.flip_h){
                for (int8_t m = 0; m <= 7; m++){
                    const uint8_t low_bit = ((sprite_bit0 >> m) & 0x01) | ((sprite_bit1 >> m)<<1 & 0x02);
                    const uint8_t palette_index = (sprite_info.u_attr.attr_bits.sprite_palette << 2) | low_bit;
                    if (palette_index%4 != 0){
                        if (sprite_info.u_attr.attr_bits.priority){
                            if (draw_data[p] == background_color){
                                draw_data[p] = nes->nes_ppu.pal.pal_split.sprite_palette[palette_index];
                            }
                        }else{
                            draw_data[p] = nes->nes_ppu.pal.pal_split.sprite_palette[palette_index];
                        }
                    }
                    if (p == 255)
                        break;
                    p++;
                }
            }else{
                for (int8_t m = 7; m >= 0; m--){
                    const uint8_t low_bit = ((sprite_bit0 >> m) & 0x01) | ((sprite_bit1 >> m)<<1 & 0x02);
                    const uint8_t palette_index = (sprite_info.u_attr.attr_bits.sprite_palette << 2) | low_bit;
                    if (palette_index%4 != 0){
                        if (sprite_info.u_attr.attr_bits.priority){
                            if (draw_data[p] == background_color){
                                draw_data[p] = nes->nes_ppu.pal.pal_split.sprite_palette[palette_index];
                            }
                        }else{
                            draw_data[p] = nes->nes_ppu.pal.pal_split.sprite_palette[palette_index];
                        }
                    }
                    if (p == 255)
                        break;
                    p++;
                }
            }
        }
        // 妫�祴绮剧伒0鍛戒腑
        if (sprite_id==0){
            const uint8_t sprite_date = sprite_bit0 | sprite_bit1;
            if (sprite_date && nes->nes_ppu.reg_mask.mask_bits.MASK_b && nes->nes_ppu.reg_status.status_bits.STATUS_S == 0){
                // printf("scanline:%d x:%d MASK_m:%d MASK_M:%d\n",
                //     scanline,nes->nes_ppu.x,nes->nes_ppu.MASK_m,nes->nes_ppu.MASK_M);
                const uint8_t nametable_id = (uint8_t)nes->nes_ppu.reg_v.v.nametable;
                const uint8_t tile_x = (nes->nes_ppu.oam.sprite_info[0].x) >> 3;
                const uint8_t tile_y = (uint8_t)(scanline >> 3);
                const uint8_t pattern_id = nes->nes_ppu.banks.bank_split.name_table[nametable_id][tile_x + (tile_y << 5)];
                const uint8_t* bit0_p = nes->nes_ppu.banks.bank_split.pattern_table[nes->nes_ppu.reg_ctrl.ctrl_bits.CTRL_B ? 4 : 0] + pattern_id * 16;
                const uint8_t* bit1_p = bit0_p + 8;
                const uint8_t background_date = bit0_p[dy] | bit1_p[dy] << 1;
                if (sprite_date & background_date){
                    nes->nes_ppu.reg_status.status_bits.STATUS_S = 1;
                    // printf("scanline:%d sprite_bit0:%d sprite_bit1:%d sprite_date:%d bit0_p:%d bit1_p:%d background_date:%d \n",
                    // scanline,sprite_bit0,sprite_bit1,sprite_date,bit0_p[dy],bit1_p[dy],background_date);
                }
            }
        }
        
    }
}

// https://www.nesdev.org/wiki/PPU_rendering


// static void nes_background_pattern_test(nes_t* nes){
//     nes_palette_generate(nes);
//     nes_memset(nes->nes_draw_data, nes->nes_ppu.background_palette[0], sizeof(nes_color_t) * NES_DRAW_SIZE);

//     uint8_t nametable_id = 0;
//     for (uint8_t j = 0; j < 16 * 8; j++){
//         uint16_t p = j*NES_WIDTH;
//         uint8_t tile_y = j/8;
//         uint8_t dy = j%8;
//         int8_t m = 7;
//         for (uint8_t i = 0; i < 16; i++){
//             uint8_t tile_x = i;
//             const uint8_t pattern_id = tile_y*16 + tile_x;
//             const uint8_t* bit0_p = nes->nes_ppu.pattern_table[1 ? 4 : 0] + pattern_id * 16;
//             const uint8_t* bit1_p = bit0_p + 8;
//             const uint8_t bit0 = bit0_p[dy];
//             const uint8_t bit1 = bit1_p[dy];
//             const uint8_t attribute = nes->nes_ppu.name_table[nametable_id][960 + ((tile_y >> 2) << 3) + (tile_x >> 2)];
//             const uint8_t high_bit = ((attribute >> (((tile_y & 2) << 1) | (tile_x & 2))) & 3) << 2;
//             for (; m >= 0; m--){
//                 uint8_t low_bit = ((bit0 >> m) & 0x01) | ((bit1 >> m)<<1 & 0x02);
//                 uint8_t palette_index = (high_bit & 0x0c) | low_bit;
//                 nes->nes_draw_data[p++] = nes->nes_ppu.background_palette[palette_index];
//             }
//             m = 7;
//         }
//     }
//     nes_draw(0, 0, NES_WIDTH-1, NES_HEIGHT-1, nes->nes_draw_data);
//     nes_frame(nes);
// }

// 单帧执行函数（非阻塞模式）
int nes_frame_step(nes_t* nes){
    if (nes->nes_quit) {
        return 0;
    }
    
#if (NES_PERF_PROFILE_ENABLE == 1)
    uint32_t t_start, t_end;
    uint32_t t_palette = 0, t_memset = 0, t_apu = 0;
    uint32_t t_render_bg = 0, t_render_sprite = 0;
    uint32_t t_opcode_total = 0;
    uint32_t t_draw = 0;
    uint32_t t_frame_end = 0;
    uint32_t t_total_start = PERF_GET_TIME();
    static uint64_t frame_cnt = 0;
    frame_cnt++;
#else
    static uint64_t frame_cnt = 0;
    frame_cnt++;
#endif

    // 1. 调色板生成
#if (NES_FRAME_SKIP != 0)
    if(nes->nes_frame_skip_count == 0)
#endif
    {
#if (NES_PERF_PROFILE_ENABLE == 1)
        t_start = PERF_GET_TIME();
#endif
        nes_palette_generate(nes);
#if (NES_PERF_PROFILE_ENABLE == 1)
        t_end = PERF_GET_TIME();
        t_palette = PERF_CALC_US(t_start, t_end);
#endif
    }

    // 2. 清除帧缓冲区
    if (nes->nes_ppu.reg_mask.mask_bits.MASK_b == 0){
#if (NES_FRAME_SKIP != 0)
        if(nes->nes_frame_skip_count == 0)
#endif
        {
#if (NES_PERF_PROFILE_ENABLE == 1)
            t_start = PERF_GET_TIME();
#endif
            nes_memset(nes->nes_draw_data, nes->nes_ppu.pal.pal_split.background_palette[0], sizeof(nes_color_t) * NES_DRAW_SIZE);
#if (NES_PERF_PROFILE_ENABLE == 1)
            t_end = PERF_GET_TIME();
            t_memset = PERF_CALC_US(t_start, t_end);
#endif
        }
    }

    // 3. APU 音频处理
#if (NES_ENABLE_SOUND==1)
#if (NES_PERF_PROFILE_ENABLE == 1)
    t_start = PERF_GET_TIME();
#endif
    nes_apu_frame(nes);
#if (NES_PERF_PROFILE_ENABLE == 1)
    t_end = PERF_GET_TIME();
    t_apu = PERF_CALC_US(t_start, t_end);
#endif
#endif
    // 4. 扫描线渲染循环
#if (NES_PERF_PROFILE_ENABLE == 1)
    uint32_t t_bg_line_start, t_bg_line_end, t_sprite_line_start, t_sprite_line_end;
    uint32_t t_opcode_start, t_opcode_end;
#endif
    // https://www.nesdev.org/wiki/PPU_rendering#Visible_scanlines_(0-239)
    for(nes->scanline = 0; nes->scanline < NES_HEIGHT; nes->scanline++) { // 0-239 Visible frame
        if (nes->nes_ppu.reg_mask.mask_bits.MASK_b){
#if (NES_FRAME_SKIP != 0)
            if (nes->nes_frame_skip_count == 0)
#endif
            {
#if (NES_PERF_PROFILE_ENABLE == 1)
                t_bg_line_start = PERF_GET_TIME();
#endif
#if (NES_RAM_LACK == 1)
                nes_render_background_line(nes, nes->scanline, nes->nes_draw_data + nes->scanline%(NES_HEIGHT/2) * NES_WIDTH);
#else
                nes_render_background_line(nes, nes->scanline, nes->nes_draw_data + nes->scanline * NES_WIDTH);
#endif
#if (NES_PERF_PROFILE_ENABLE == 1)
                t_bg_line_end = PERF_GET_TIME();
                t_render_bg += PERF_CALC_US(t_bg_line_start, t_bg_line_end);
#endif
            }
        }
        if (nes->nes_ppu.reg_mask.mask_bits.MASK_s){
#if (NES_PERF_PROFILE_ENABLE == 1)
            t_sprite_line_start = PERF_GET_TIME();
#endif
#if (NES_RAM_LACK == 1)
            nes_render_sprite_line(nes, nes->scanline,nes->nes_draw_data + nes->scanline%(NES_HEIGHT/2) * NES_WIDTH);
#else
            nes_render_sprite_line(nes, nes-> scanline,nes->nes_draw_data + nes->scanline * NES_WIDTH);
#endif
#if (NES_PERF_PROFILE_ENABLE == 1)
            t_sprite_line_end = PERF_GET_TIME();
            t_render_sprite += PERF_CALC_US(t_sprite_line_start, t_sprite_line_end);
#endif
        }

        // CPU 指令执行（扫描线内）
#if (NES_PERF_PROFILE_ENABLE == 1)
        t_opcode_start = PERF_GET_TIME();
#endif
        nes_opcode(nes,85); // ppu cycles: 85*3=255
#if (NES_PERF_PROFILE_ENABLE == 1)
        t_opcode_end = PERF_GET_TIME();
        t_opcode_total += PERF_CALC_US(t_opcode_start, t_opcode_end);
#endif
            // https://www.nesdev.org/wiki/PPU_scrolling#Wrapping_around
            if (nes->nes_ppu.reg_mask.mask_bits.MASK_b){
                // https://www.nesdev.org/wiki/PPU_scrolling#At_dot_256_of_each_scanline
                if ((nes->nes_ppu.reg_v.v.fine_y) < 7) {
                    nes->nes_ppu.reg_v.v.fine_y++;
                }else {
                    nes->nes_ppu.reg_v.v.fine_y = 0;
                    uint8_t y = (uint8_t)(nes->nes_ppu.reg_v.v.coarse_y);
                    if (y == 29) {
                        y = 0;
                        nes->nes_ppu.reg_v.v_reg ^= 0x0800;
                    }else if (y == 31) {
                        y = 0;
                    }else {
                        y++;
                    }
                    nes->nes_ppu.reg_v.v.coarse_y = y;
                }
                // https://www.nesdev.org/wiki/PPU_scrolling#At_dot_257_of_each_scanline
                // v: ....A.. ...BCDEF <- t: ....A.. ...BCDEF
                nes->nes_ppu.reg_v.v_reg = (nes->nes_ppu.reg_v.v_reg & (uint16_t)0xFBE0) | (nes->nes_ppu.reg_t.t_reg & (uint16_t)0x041F);
            }
#if (NES_PERF_PROFILE_ENABLE == 1)
        t_opcode_start = PERF_GET_TIME();
#endif
        nes_opcode(nes,NES_PPU_CPU_CLOCKS-85);
#if (NES_PERF_PROFILE_ENABLE == 1)
        t_opcode_end = PERF_GET_TIME();
        t_opcode_total += PERF_CALC_US(t_opcode_start, t_opcode_end);
#endif

#if (NES_ENABLE_SOUND==1)
        if (nes->scanline % 66 == 65) {
#if (NES_PERF_PROFILE_ENABLE == 1)
            t_start = PERF_GET_TIME();
#endif
            nes_apu_frame(nes);
#if (NES_PERF_PROFILE_ENABLE == 1)
            t_end = PERF_GET_TIME();
            t_apu += PERF_CALC_US(t_start, t_end);
#endif
        }
#endif

#if (NES_RAM_LACK == 1)
#if (NES_FRAME_SKIP != 0)
        if(nes->nes_frame_skip_count == 0)
#endif
        {
            if (nes->scanline == NES_HEIGHT/2-1){
#if (NES_PERF_PROFILE_ENABLE == 1)
                t_start = PERF_GET_TIME();
#endif
                nes_draw(0, 0, NES_WIDTH-1, NES_HEIGHT/2-1, nes->nes_draw_data);
#if (NES_PERF_PROFILE_ENABLE == 1)
                t_end = PERF_GET_TIME();
                t_draw += PERF_CALC_US(t_start, t_end);
#endif
            }else if(nes->scanline == NES_HEIGHT-1){
#if (NES_PERF_PROFILE_ENABLE == 1)
                t_start = PERF_GET_TIME();
#endif
                nes_draw(0, NES_HEIGHT/2, NES_WIDTH-1, NES_HEIGHT-1, nes->nes_draw_data);
#if (NES_PERF_PROFILE_ENABLE == 1)
                t_end = PERF_GET_TIME();
                t_draw += PERF_CALC_US(t_start, t_end);
#endif
            }
        }
#endif
    }

#if (NES_RAM_LACK == 0)
#if (NES_FRAME_SKIP != 0)
    if(nes->nes_frame_skip_count == 0)
#endif
    {
#if (NES_PERF_PROFILE_ENABLE == 1)
        t_start = PERF_GET_TIME();
#endif
        nes_draw(0, 0, NES_WIDTH-1, NES_HEIGHT-1, nes->nes_draw_data);
#if (NES_PERF_PROFILE_ENABLE == 1)
        t_end = PERF_GET_TIME();
        t_draw = PERF_CALC_US(t_start, t_end);
#endif
    }
#endif

    // 5. Post-render 处理
#if (NES_PERF_PROFILE_ENABLE == 1)
    t_opcode_start = PERF_GET_TIME();
#endif
    nes_opcode(nes,NES_PPU_CPU_CLOCKS); //240 Post-render line
#if (NES_PERF_PROFILE_ENABLE == 1)
    t_opcode_end = PERF_GET_TIME();
    t_opcode_total += PERF_CALC_US(t_opcode_start, t_opcode_end);
#endif
        
        nes->nes_ppu.reg_status.status_bits.STATUS_V = 1;// Set VBlank flag (241 line)
        if (nes->nes_ppu.reg_ctrl.ctrl_bits.CTRL_V) {
            nes->nes_cpu.irq_nmi=1;
        }

        for(uint8_t i = 0; i < 20; i++){ // 241-260琛�鍨傜洿绌虹櫧琛�x20
            nes_opcode(nes,NES_PPU_CPU_CLOCKS);
        }
        nes->nes_ppu.reg_status.ppu_status = 0;    // Clear:VBlank,Sprite 0,Overflow
        nes_opcode(nes,NES_PPU_CPU_CLOCKS); // Pre-render scanline (-1 or 261)

    if (nes->nes_ppu.reg_mask.mask_bits.MASK_b){
        // https://www.nesdev.org/wiki/PPU_scrolling#During_dots_280_to_304_of_the_pre-render_scanline_(end_of_vblank)
        // v: GHIA.BC DEF..... <- t: GHIA.BC DEF.....
        nes->nes_ppu.reg_v.v_reg = (nes->nes_ppu.reg_v.v_reg & (uint16_t)0x841F) | (nes->nes_ppu.reg_t.t_reg & (uint16_t)0x7BE0);
    }

#if (NES_PERF_PROFILE_ENABLE == 1)
    t_start = PERF_GET_TIME();
#endif
    nes_frame(nes);
#if (NES_PERF_PROFILE_ENABLE == 1)
    t_end = PERF_GET_TIME();
    t_frame_end = PERF_CALC_US(t_start, t_end);
#endif
#if (NES_FRAME_SKIP != 0)
    if ( ++nes->nes_frame_skip_count > NES_FRAME_SKIP){
        nes->nes_frame_skip_count = 0;
    }
#endif

#if (NES_PERF_PROFILE_ENABLE == 1)
    // 计算总时间
    uint32_t t_total_end = PERF_GET_TIME();
    uint32_t t_total_us = PERF_CALC_US(t_total_start, t_total_end);
    uint32_t t_total_ms = PERF_CALC_MS(t_total_start, t_total_end);

    // 每 60 帧打印一次性能数据（约每秒一次，如果每帧16ms）
    // 注意：如果实际每帧耗时更长，打印间隔也会更长
    if ((frame_cnt % 60) == 0) {
        printf("\r\n=== NES Perf Frame: ");
        // 使用最简单的数字打印
        uint32_t fn = (uint32_t)(frame_cnt);
        char num[12];
        int pos = 0;
        if (fn == 0) {
            num[pos++] = '0';
        } else {
            uint32_t n = fn;
            while (n > 0 && pos < 11) {
                num[pos++] = '0' + (n % 10);
                n /= 10;
            }
        }
        while (pos > 0) {
            printf("%c", num[--pos]);
        }
        printf(" ===\r\n");
        
        // 计算每帧平均值（60帧累计值除以60）
        int per_frame_total = (int)(t_total_us / 60);
        int per_frame_opcode = (int)(t_opcode_total / 60);
        int per_frame_lcd = (int)(t_draw / 60);
        int per_frame_bg = (int)(t_render_bg / 60);
        int per_frame_sprite = (int)(t_render_sprite / 60);
        int per_frame_palette = (int)(t_palette / 60);
        int per_frame_frame_end = (int)(t_frame_end / 60);
        
        printf("--- Per Frame Avg (from 60 frames) ---\r\n");
        printf("Total/Frame: %d us", per_frame_total);
        if (per_frame_total > 0) {
            int ms = per_frame_total / 1000;
            int frac = (per_frame_total % 1000) / 10;
            printf(" (%d.%02d ms)", ms, frac);
        }
        printf("\r\n");
        
        printf("  Palette: %d us\r\n", per_frame_palette);
        printf("  BG Render: %d us", per_frame_bg);
        if (per_frame_bg > 0 && NES_HEIGHT > 0) {
            int avg = (per_frame_bg * 100) / NES_HEIGHT;
            printf(" (~%d.%02d us/line)", avg / 100, avg % 100);
        }
        printf("\r\n");
        
        printf("  Sprite Render: %d us", per_frame_sprite);
        if (per_frame_sprite > 0 && NES_HEIGHT > 0) {
            int avg = (per_frame_sprite * 100) / NES_HEIGHT;
            printf(" (~%d.%02d us/line)", avg / 100, avg % 100);
        }
        printf("\r\n");
        
        printf("  CPU Opcode: %d us", per_frame_opcode);
        printf(" (NOTE: This is accumulated time from multiple calls)\r\n");
        printf("  LCD Draw: %d us  <-- MAIN BOTTLENECK!\r\n", per_frame_lcd);
        printf("  Frame End: %d us\r\n", per_frame_frame_end);
        
        // 计算实际瓶颈（CPU Opcode 时间可能与其他操作重叠）
        // 实际瓶颈 = 总时间减去其他操作
        int real_cpu_time = per_frame_total - per_frame_lcd - per_frame_bg - per_frame_sprite - per_frame_palette - per_frame_frame_end;
        if (real_cpu_time < 0) real_cpu_time = 0;
        
        printf("\r\nActual Time Breakdown:\r\n");
        printf("  Total/Frame: %d ms\r\n", per_frame_total / 1000);
        printf("  LCD Draw: %d ms (%d%%)\r\n", per_frame_lcd / 1000, (per_frame_lcd * 100) / per_frame_total);
        printf("  BG Render: %d ms (%d%%)\r\n", per_frame_bg / 1000, (per_frame_bg * 100) / per_frame_total);
        printf("  Sprite: %d ms (%d%%)\r\n", per_frame_sprite / 1000, (per_frame_sprite * 100) / per_frame_total);
        printf("  CPU+Other: %d ms (%d%%)\r\n", real_cpu_time / 1000, (real_cpu_time * 100) / per_frame_total);
        printf("\r\nNote: CPU Opcode accumulated time (%d ms) is larger than frame time\r\n", per_frame_opcode / 1000);
        printf("      because CPU instructions execute in parallel with rendering.\r\n");
        
        printf("\r\n--- Total (60 frames cumulative) ---\r\n");
        printf("Total: %d us\r\n", (int)t_total_us);
        printf("  BG Render: %d us\r\n", (int)t_render_bg);
        printf("  Sprite: %d us\r\n", (int)t_render_sprite);
        printf("  CPU Opcode: %d us\r\n", (int)t_opcode_total);
        printf("  LCD Draw: %d us\r\n", (int)t_draw);
        printf("==========================\r\n");
    }
#endif
    
    return 1; // 返回 1 表示还有下一帧
}

// 原有的阻塞式运行函数（保留用于兼容，但建议使用定时器+nes_frame_step）
void nes_run(nes_t* nes){
    printf("mapper:%03d\n",nes->nes_rom.mapper_number);
    printf("prg_rom_size:%d*16kB\n",nes->nes_rom.prg_rom_size);
    printf("chr_rom_size:%d*8kB\n",nes->nes_rom.chr_rom_size);
    printf("mirroring_type:%d\n",nes->nes_rom.mirroring_type);
    printf("four_screen:%d\n",nes->nes_rom.four_screen);
    printf("save_ram:%d\n",nes->nes_rom.save_ram);

    nes_cpu_reset(nes);
    
    while (!nes->nes_quit){
        if (!nes_frame_step(nes)) {
            break;
        }
    }
}

